Support recursive queries against LDAP directories The top domain of a company might be DC=example,DC=com, while a sub-domain may be DC=asia,DC=example,DC=com. Assuming all users in the company will use Gerrit, but their accounts are in different sub-domains, recursive search under DC=example,DC=com is required. So ldap.accountScope can be set to subtree for this case. Change-Id: I9c6f98148b00e7c3f1e197054efcb2899f370d74 Signed-off-by: Shawn O. Pearce <sop@google.com>
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt index 13384e1..80f5191 100644 --- a/Documentation/config-gerrit.txt +++ b/Documentation/config-gerrit.txt
@@ -514,6 +514,17 @@ Root of the tree containing all user accounts. This is typically of the form `ou=people,dc=example,dc=com`. +[[ldap.accountScope]]ldap.accountScope:: ++ +Scope of the search performed for accounts. Must be one of: ++ +* `one`: Search only one level below accountBase, but not recursive +* `sub` or `subtree`: Search recursively below accountBase +* `base` or `object`: Search exactly accountBase; probably not desired + ++ +Default is `subtree` as many directories have several levels. + [[ldap.accountPattern]]ldap.accountPattern:: + Query pattern to use when searching for a user account. This may be @@ -572,6 +583,17 @@ Root of the tree containing all group objects. This is typically of the form `ou=groups,dc=example,dc=com`. +[[ldap.groupScope]]ldap.groupScope:: ++ +Scope of the search performed for group objects. Must be one of: ++ +* `one`: Search only one level below groupBase, but not recursive +* `sub` or `subtree`: Search recursively below groupBase +* `base` or `object`: Search exactly groupBase; probably not desired + ++ +Default is `subtree` as many directories have several levels. + [[ldap.groupName]]ldap.groupName:: + Name of an attribute on the group object which matches to the name
diff --git a/src/main/java/com/google/gerrit/server/ldap/LdapQuery.java b/src/main/java/com/google/gerrit/server/ldap/LdapQuery.java index 4a03335..b0eb938 100644 --- a/src/main/java/com/google/gerrit/server/ldap/LdapQuery.java +++ b/src/main/java/com/google/gerrit/server/ldap/LdapQuery.java
@@ -29,14 +29,45 @@ /** Supports issuing parameterized queries against an LDAP data source. */ class LdapQuery { + static enum SearchScope { + // Search only the base DN + // + OBJECT(SearchControls.OBJECT_SCOPE), // + BASE(SearchControls.OBJECT_SCOPE), + + // Search all entries one level under the base DN + // + // Does not include the base DN, and does not include items below items + // under the base DN. + // + ONE(SearchControls.ONELEVEL_SCOPE), + + // Search all entries under the base DN, including the base DN. + // + SUBTREE(SearchControls.SUBTREE_SCOPE), // + SUB(SearchControls.SUBTREE_SCOPE); + + private final int scope; + + SearchScope(final int scope) { + this.scope = scope; + } + + int scope() { + return scope; + } + } + private final String base; + private final SearchScope searchScope; private final String pattern; private final String[] patternArgs; private final String[] returnAttributes; - LdapQuery(final String base, final String pattern, - final Set<String> returnAttributes) { + LdapQuery(final String base, final SearchScope searchScope, + final String pattern, final Set<String> returnAttributes) { this.base = base; + this.searchScope = searchScope; final StringBuilder p = new StringBuilder(); final List<String> a = new ArrayList<String>(4); @@ -76,7 +107,7 @@ final SearchControls sc = new SearchControls(); final NamingEnumeration<SearchResult> res; - sc.setSearchScope(SearchControls.ONELEVEL_SCOPE); + sc.setSearchScope(searchScope.scope()); sc.setReturningAttributes(returnAttributes); res = ctx.search(base, pattern, bind(params), sc); try { diff --git a/src/main/java/com/google/gerrit/server/ldap/LdapRealm.java b/src/main/java/com/google/gerrit/server/ldap/LdapRealm.java index 2d0d2a0..e127cd1 100644 --- a/src/main/java/com/google/gerrit/server/ldap/LdapRealm.java +++ b/src/main/java/com/google/gerrit/server/ldap/LdapRealm.java
@@ -26,7 +26,9 @@ import com.google.gerrit.server.account.Realm; import com.google.gerrit.server.cache.Cache; import com.google.gerrit.server.cache.SelfPopulatingCache; +import com.google.gerrit.server.config.ConfigUtil; import com.google.gerrit.server.config.GerritServerConfig; +import com.google.gerrit.server.ldap.LdapQuery.SearchScope; import com.google.gwtorm.client.OrmException; import com.google.gwtorm.client.SchemaFactory; import com.google.inject.Inject; @@ -96,9 +98,11 @@ groupName = reqdef(config, "groupName", "cn"); groupAtts.add(groupName); final String groupBase = required(config, "groupBase"); + final SearchScope groupScope = scope(config, "groupScope"); final String groupMemberPattern = reqdef(config, "groupMemberPattern", "(memberUid=${username})"); - groupMemberQuery = new LdapQuery(groupBase, groupMemberPattern, groupAtts); + groupMemberQuery = + new LdapQuery(groupBase, groupScope, groupMemberPattern, groupAtts); if (groupMemberQuery.getParameters().length == 0) { throw new IllegalArgumentException( "No variables in ldap.groupMemberPattern"); @@ -140,9 +144,11 @@ } } final String accountBase = required(config, "accountBase"); + final SearchScope accountScope = scope(config, "accountScope"); final String accountPattern = reqdef(config, "accountPattern", "(uid=${username})"); - accountQuery = new LdapQuery(accountBase, accountPattern, accountAtts); + accountQuery = + new LdapQuery(accountBase, accountScope, accountPattern, accountAtts); if (accountQuery.getParameters().length == 0) { throw new IllegalArgumentException("No variables in ldap.accountPattern"); } @@ -155,6 +161,10 @@ }; } + private static SearchScope scope(final Config c, final String setting) { + return ConfigUtil.getEnum(c, "ldap", null, setting, SearchScope.SUBTREE); + } + private static String optional(final Config config, final String name) { return config.getString("ldap", null, name); }